<!DocType html>
<style>
body {
    margin: 4px;
}

body > p:first-of-type {
    margin-top: 0;
}

tr:first-of-type:hover {
    opacity: 0.7
}

thead, tbody {
    background-color: #E3E9FF;
}

td {
    padding: 0 4px;
}

th:empty, td:empty {
    padding: 0;
}

th {
    -webkit-user-select: none;
    -moz-user-select: none;
}

label {
    margin-left: 10px;
}

.results-row {
    background-color: white;
}

.results-row iframe {
    width: 800px;
    height: 600px;
}

#options {
    position: absolute;
    top: 4px;
    right: 4px;
}

.expand-button {
    background-color: white;
    color: blue;
    width: 11px;
    height: 11px;
    border: 1px solid blue;
    display: inline-block;
    margin: 0 3px 0 0;
    position: relative;
}

.expand-button-text {
    position: absolute;
    top: -0.3em;
    left: 1px;
}

.result-container {
    display: inline-block;
    border: 1px solid gray;
}

.result-container iframe, .result-container img {
    border: 0;
    border-top: 1px solid lightgray;
    vertical-align: top;
}

.label {
    padding-left: 3px;
    font-weight: bold;
    font-size: small;
}

.pixel-zoom-container {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    display: -webkit-box;
}

.pixel-zoom-container > * {
    display: -webkit-box;
    -webkit-box-flex: 1;
    border: 1px inset lightgray;
    height: 100px;
    overflow: hidden;
    zoom: 300%;
    background-color: white;
}

.pixel-zoom-container img {
    width: 800px;
    height: 600px;
    vertical-align: top;
}
</style>

<script>
var g_results;
function ADD_RESULTS(input)
{
    g_results = input;
}
</script>

<script src="full_results.json"></script>

<script>
function stripExtension(test)
{
    var index = test.lastIndexOf('.');
    return test.substring(0, index);
}

function parentOfType(node, selector)
{
    while (node = node.parentElement) {
        if (node.webkitMatchesSelector(selector))
            return node;
    }
    return null;
}

function appendResultIframe(src, parent)
{
    // FIXME: use audio tags for AUDIO tests?
    var layoutTestsIndex = src.indexOf('LayoutTests');
    var name;
    if (layoutTestsIndex != -1) {
        var hasTrac = src.indexOf('trac.webkit.org') != -1;
        var prefix = hasTrac ? 'trac.webkit.org/.../' : '';
        name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length);
    } else {
        var lastDashIndex = src.lastIndexOf('-pretty');
        if (lastDashIndex == -1)
            lastDashIndex = src.lastIndexOf('-');
        name = src.substring(lastDashIndex + 1);
    }

    var tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img';

    var container = document.createElement('div');
    container.className = 'result-container';
    container.innerHTML = '<div class=label>' + name + '</div><' + tagName + ' src="' + src + '?format=txt"></' + tagName + '>';
    parent.appendChild(container);
}

function expandExpectations(e)
{
    var expandLink = e.target;
    if (expandLink.className != 'expand-button-text')
        expandLink = expandLink.querySelector('.expand-button-text');

    var isExpand = expandLink.textContent == '+';
    var row = parentOfType(expandLink, 'tr');
    var parentTbody = row.parentNode;
    var existingResultsRow = parentTbody.querySelector('.results-row');
    
    if (!isExpand) {
        expandLink.textContent = '+';
        existingResultsRow.style.display = 'none';
        return;
    }
    
    var enDash = '\u2013';
    expandLink.textContent = enDash;
    if (existingResultsRow) {
        existingResultsRow.style.display = '';
        return;
    }
    
    var newRow = document.createElement('tr');
    newRow.className = 'results-row';
    var newCell = document.createElement('td');
    newCell.colSpan = row.querySelectorAll('td').length;

    appendResultIframe(row.querySelector('.test-link').href, newCell);

    var resultLinks = row.querySelectorAll('.result-link');
    for (var i = 0; i < resultLinks.length; i++)
        appendResultIframe(resultLinks[i].href, newCell);

    newRow.appendChild(newCell);
    parentTbody.appendChild(newRow);
}

function testLink(test)
{
    var basePath;
    if (g_results.layout_tests_dir && location.toString().indexOf('file://') == 0)
        basePath = g_results.layout_tests_dir + '/';
    else
        basePath = 'http://trac.webkit.org/browser/trunk/LayoutTests/';
    return '<span class=expand-button onclick="expandExpectations(event)"><span class=expand-button-text>+</span></span>' +
        '<a class=test-link href="' + basePath + test + '">' + test + '</a>';
}

function resultLink(testPrefix, suffix, contents)
{
    return '<a class=result-link href="' + testPrefix + suffix + '">' + contents + '</a> ';
}

var g_hasTextFailures = false;
var g_hasImageFailures = false;

var g_testsWithStderr = [];
var g_newTests = [];
var g_hasHttpTests = false;

function tableRows()
{
    var html = '';
    for (var test in g_results.tests) {
        if (g_results.tests[test].has_stderr)
            g_testsWithStderr.push(test);

        g_hasHttpTests = g_hasHttpTests || test.indexOf('http/') == 0;

        var actual = g_results.tests[test].actual;
        if (actual == 'MISSING') {
            // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for
            // tests with MISSING results.
            g_newTests.push(test);
            continue;
        }

        var expected = g_results.tests[test].expected || 'PASS';
        if (actual == 'PASS' && (!g_results.uses_expectations_file || expected == 'PASS'))
          continue;

        // FIXME: put unexpected passes in a separate table.

        var row = '<td>' + testLink(test) + '</td>';
        var test_prefix = stripExtension(test);

        row += '<td>';
        if (actual == 'CRASH')
            row += resultLink(test_prefix, '-stack.txt', 'stack');
        else if (actual == 'AUDIO') {
            row += resultLink(test_prefix, '-expected.wav', 'expected');
            row += resultLink(test_prefix, '-actual.wav', 'actual');
        } else if (actual.indexOf('TEXT') != -1 || actual == 'TIMEOUT') {
            // FIXME: only include timeout actual/expected results here if we actually spit out results for timeout tests.
            g_hasTextFailures = true;
            row += resultLink(test_prefix, '-expected.txt', 'expected') +
                resultLink(test_prefix, '-actual.txt', 'actual') +
                resultLink(test_prefix, '-diff.txt', 'diff');
      
            if (g_results.has_pretty_patch)
                row += resultLink(test_prefix, '-pretty-diff.html', 'pretty diff');

            if (g_results.has_wdiff)
                row += resultLink(test_prefix, '-wdiff.html', 'wdiff');
        }

        row += '</td><td>';

        if (actual.indexOf('IMAGE') != -1) {
            g_hasImageFailures = true;

            if (g_results.tests[test].is_mismatch_reftest) {
                row += resultLink(test_prefix, '-expected-mismatch.html', 'ref mismatch html') +
                    resultLink(test_prefix, '-actual.png', 'actual');
            } else {
                if (g_results.tests[test].is_reftest)
                    row += resultLink(test_prefix, '-expected.html', 'ref html');

                row += resultLink(test_prefix, '-expected.png', 'expected') +
                    resultLink(test_prefix, '-actual.png', 'actual') +
                    resultLink(test_prefix, '-diff.png', 'diff');
            }
        }

        row += '</td>';
        row += '<td>' + actual + '</td>';

        if (g_results.uses_expectations_file)
          row += '<td>' + expected + '</td>';

        var isExpected = actual == 'SKIP';
        if (!isExpected && g_results.uses_expectations_file) {
            var expectedArray = expected.split(' ');
            if (expectedArray.indexOf(actual) != -1)
                isExpected = true;
            else if (expectedArray.indexOf('FAIL') != -1)
                isExpected = actual == 'IMAGE' || actual == 'TEXT' || actual == 'IMAGE+TEXT';
        }
        html += '<tbody class="' + (isExpected ? 'expected' : '') + '"><tr>' + row + '</tr></tbody>';
    }
    return html;
}

var html = '';
if (g_results.uses_expectations_file)
    html += '<div id=options><label><input class="unexpected-results" type=checkbox checked>Only show unexpected results</label></div>';

var tableRowsHtml = tableRows();

if (tableRowsHtml) {
    html += '<p>Tests where results did not match expected results:</p>' +
        '<table id="results-table"><thead><tr>' +
        '<th>test</th>' +
        '<th id="text-results-header">text results</th>' +
        '<th id="image-results-header">image results</th>' +
        '<th>failure type</th>';
        
    if (g_results.uses_expectations_file)
        html += '<th>expected failure type</th>';

    html += '</tr></thead>' + tableRowsHtml + '</table>';
}

function appendTestList(tests, header, tableId, fileSuffix, linkName)
{
    tests.sort();

    html += '<p>' + header + '</p><table id="' + tableId + '">';
    for (var i = 0; i < tests.length; i++) {
        var test = tests[i];
        html += '<tbody><tr><td>' + testLink(test) + '</td><td>';
        
        if (fileSuffix.indexOf('actual') == -1)
            html += resultLink(stripExtension(test), fileSuffix, linkName);
        else {
            var testObject = g_results.tests[test];
            if (testObject.is_missing_audio)
                html += resultLink(stripExtension(test), '-actual.wav', 'audio result');
            if (testObject.is_missing_text)
                html += resultLink(stripExtension(test), fileSuffix, linkName);
            if (testObject.is_missing_image)
                html += resultLink(stripExtension(test), '-actual.png', 'png result');
        } 
        
        html += '</td></tr></tbody>';
    }
    html += '</table>'
}

if (g_newTests.length)
    appendTestList(g_newTests, 'Tests that had no expected results (probably new):', 'new-tests-table', '-actual.txt', 'result');

if (g_testsWithStderr.length)
    appendTestList(g_testsWithStderr, 'Tests that had stderr output:', 'stderr-table', '-stderr.txt', 'stderr');

if (g_hasHttpTests) {
    html += '<p>httpd access log: <a href="access_log.txt">access_log.txt</a></p>' +
        '<p>httpd error log: <a href="error_log.txt">error_log.txt</a></p>';
}

document.write(html);

function toArray(nodeList)
{
    return Array.prototype.slice.call(nodeList);
}

function trim(string)
{
    return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
}

// Just a namespace for code management.
var TableSorter = {};

TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#aaa"></svg>';

TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#aaa"></svg>';

TableSorter._sortedContents = function(header, arrow)
{
    return arrow + ' ' + trim(header.textContent) + ' ' + arrow;
}

TableSorter._updateHeaderClassNames = function(newHeader)
{
    var sortHeader = document.querySelector('.sortHeader');
    if (sortHeader) {
        if (sortHeader == newHeader) {
            var isAlreadyReversed = sortHeader.classList.contains('reversed');
            if (isAlreadyReversed)
                sortHeader.classList.remove('reversed');
            else
                sortHeader.classList.add('reversed');
        } else {
            sortHeader.textContent = sortHeader.textContent;
            sortHeader.classList.remove('sortHeader');
            sortHeader.classList.remove('reversed');
        }
    }

    newHeader.classList.add('sortHeader');
}

TableSorter._textContent = function(tbodyRow, column)
{
    return tbodyRow.querySelectorAll('td')[column].textContent;
}

TableSorter._sortRows = function(newHeader, reversed)
{
    var testsTable = document.getElementById('results-table');
    var headers = toArray(testsTable.querySelectorAll('th'));
    var sortColumn = headers.indexOf(newHeader);

    var rows = toArray(testsTable.querySelectorAll('tbody'));

    rows.sort(function(a, b) {
        // Only need to support lexicographic sort for now.
        var aText = TableSorter._textContent(a, sortColumn);
        var bText = TableSorter._textContent(b, sortColumn);
        
        // Forward sort equal values by test name.
        if (sortColumn && aText == bText) {
            var aTestName = TableSorter._textContent(a, 0);
            var bTestName = TableSorter._textContent(b, 0);
            if (aTestName == bTestName)
                return 0;
            return aTestName < bTestName ? -1 : 1;
        }

        if (reversed)
            return aText < bText ? 1 : -1;
        else
            return aText < bText ? -1 : 1;
    });

    for (var i = 0; i < rows.length; i++)
        testsTable.appendChild(rows[i]);
}

TableSorter.sortColumn = function(columnNumber)
{
    var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
    TableSorter._sort(newHeader);
}

TableSorter.handleClick = function(e)
{
    var newHeader = e.target;
    if (newHeader.localName != 'th')
        return;
    TableSorter._sort(newHeader);
}

TableSorter._sort = function(newHeader)
{
    TableSorter._updateHeaderClassNames(newHeader);
    
    var reversed = newHeader.classList.contains('reversed');
    var sortArrow = reversed ? TableSorter._backwardArrow : TableSorter._forwardArrow;
    newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow);
    
    TableSorter._sortRows(newHeader, reversed);
}

if (document.getElementById('results-table'))
    document.getElementById('results-table').addEventListener('click', TableSorter.handleClick, false);
TableSorter.sortColumn(0);

var PixelZoomer = {};

PixelZoomer._createContainer = function(e)
{
    var tbody = parentOfType(e.target, 'tbody');
    var imageDiffLinks = tbody.querySelector('tr').querySelectorAll('a[href$=".png"]');
    
    var container = document.createElement('div');
    container.className = 'pixel-zoom-container';
    
    var html = '';
    for (var i = 0; i < imageDiffLinks.length; i++)
        html += '<div class=zoom-image-container><img src="' + imageDiffLinks[i].href + '"></div>';
    
    container.innerHTML = html;
    document.body.appendChild(container);

    PixelZoomer._position(e);
}

PixelZoomer._position = function(e)
{
    var pageX = e.clientX;
    var pageY = e.clientY;
    var targetLocation = e.target.getBoundingClientRect();
    var x = pageX - targetLocation.left;
    var y = pageY - targetLocation.top;

    var zoomContainers = document.querySelectorAll('.pixel-zoom-container > .zoom-image-container');
    for (var i = 0; i < zoomContainers.length; i++) {
        var container = zoomContainers[i];
        container.scrollLeft = x - container.offsetWidth / 2;
        container.scrollTop = y - container.offsetHeight / 2;
    }
}

PixelZoomer.handleMouseMove = function(e) {
    if (PixelZoomer._mouseMoveTimeout)
        clearTimeout(PixelZoomer._mouseMoveTimeout);

    if (parentOfType(e.target, '.pixel-zoom-container'))
        return;

    var container = document.querySelector('.pixel-zoom-container');
    if (!e.target.src || e.target.src.indexOf('.png') == -1) {
        if (container)
            container.parentNode.removeChild(container);
        return;
    }
    
    if (!container) {
        PixelZoomer._mouseMoveTimeout = setTimeout(function() {
            PixelZoomer._createContainer(e);
        }, 200);
        return;
    }
    
    PixelZoomer._position(e);
}

document.body.addEventListener('mousemove', PixelZoomer.handleMouseMove, false);


var unexpectedStyleNode = document.createElement('style');
document.body.appendChild(unexpectedStyleNode);

function updateExpectedResults()
{
    var checkBox = document.querySelector('.unexpected-results');
    if (!checkBox || checkBox.checked)
        unexpectedStyleNode.innerText = '.expected { display: none; }';
    else
        unexpectedStyleNode.innerText = '';
}

updateExpectedResults();
if (document.querySelector('.unexpected-results'))
    document.querySelector('.unexpected-results').addEventListener('change', updateExpectedResults, false);

if (!g_hasTextFailures)
  document.body.getElementById('text-results-header').textContent = '';
if (!g_hasImageFailures)
  document.body.getElementById('image-results-header').textContent = '';
</script>
